package com.zusmart.base.network.nio;

import java.nio.channels.SocketChannel;

import com.zusmart.base.activity.ActivityUtils;
import com.zusmart.base.buffer.BufferAllocator;
import com.zusmart.base.buffer.support.DefaultBufferAllocator;
import com.zusmart.base.network.ChannelAdapter;
import com.zusmart.base.network.ChannelContext;
import com.zusmart.base.network.ChannelContextFuture;
import com.zusmart.base.network.ChannelOption;
import com.zusmart.base.network.handler.ChannelContextHandlerContext;
import com.zusmart.base.network.handler.support.DefaultChannelContextHandler;
import com.zusmart.base.network.support.AbstractChannelConnector;
import com.zusmart.base.toolkit.NetAddress;
import com.zusmart.base.util.Assert;

public class NioChannelConnector extends AbstractChannelConnector {

	private NetAddress connectAddress;
	private NioChannelContext channelContext;
	private BufferAllocator bufferAllocator;
	private NioChannelContextManager channelContextManager;
	private NioChannelEventLoopGroup channelEventLoopGroup;

	public NioChannelConnector(NetAddress connectAddress, ChannelOption channelOption, ChannelAdapter channelAdapter) {
		super(channelOption, channelAdapter);
		Assert.isNull(connectAddress, "connect address must not be null");
		this.connectAddress = connectAddress;
	}

	@Override
	public boolean isOpen() {
		return null != this.channelContext && this.channelContext.isOpen();
	}

	@Override
	public NetAddress getServerAddress() {
		return this.isOpen() ? this.channelContext.getServerAddress() : null;
	}

	@Override
	public NetAddress getClientAddress() {
		return this.isOpen() ? this.channelContext.getClientAddress() : null;
	}

	@Override
	public ChannelContext getChannelContext() {
		return this.channelContext;
	}

	@Override
	protected void doStart() throws Exception {
		ChannelOption option = this.getChannelOption();
		SocketChannel channel = SocketChannel.open();
		this.bufferAllocator = new DefaultBufferAllocator();
		this.channelEventLoopGroup = new NioChannelEventLoopGroup(option, this.bufferAllocator);
		this.channelContextManager = new NioChannelContextManager(false, this.getChannelAdapter(), this.channelEventLoopGroup);
		channel = SocketChannel.open();
		channel.socket().setSoTimeout(option.getSoTimeout());
		channel.socket().setTcpNoDelay(option.isTcpNoDelay());
		channel.socket().setKeepAlive(option.isTcpKeepAlive());
		channel.connect(this.connectAddress.asSocketAddress());
		this.channelContextManager.start();
		this.channelEventLoopGroup.start();
		this.channelContext = this.channelContextManager.createChannelContext(channel);
		this.channelContext.getChannelContextHandlerChain().addFirst("__CONNECTOR_MONITOR__", new DefaultChannelContextHandler() {
			@Override
			public void unRegister(ChannelContext channelContext, ChannelContextHandlerContext handlerContext) {
				super.unRegister(channelContext, handlerContext);
				ActivityUtils.close(NioChannelConnector.this);
			}
		});
		ChannelContextFuture future = this.channelContext.getNioChannelEventLoop().doRegister(this.channelContext);
		future.waitComplete(option.getConnectTimeout());
		if (!future.isSuccessed()) {
			throw new Exception(future.getFailureCause());
		}
	}

	@Override
	protected void doClose() throws Exception {
		if (null != this.channelContext) {
			this.channelContext.close();
		}
		ActivityUtils.close(channelContextManager);
		ActivityUtils.close(channelEventLoopGroup);
	}

}